#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/vaddr.h"
#include "filesys/fdtable.h"
#include "devices/input.h"
#include "userprog/process.h"
#include "filesys/filesys.h"

static void syscall_handler (struct intr_frame *);

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

int sane_file_name(const char *name)
{
  if (name) {
    while (is_user_access_ok(name, 1)) {
	//	printf("%c-", *name); 
      if (*name++ == '\0') return 1;
	  }
  }
  return 0;
}
#if 0
int is_fd_ok(struct thread *cur, int fd)
{
  struct files_struct *fs = cur->files;
  int ret = 1;

  if (fs == NULL) ret = 0;

  lock_acquire(&fs->file_lock);

  if (fd < 0) ret = 0;
  else if (fd == 0 || fd == 1) ret = 1;
  else if (fd >= fs->next_fd) ret = 0;
  else if ((fd < fs->next_fd) &&  // fd doesn't correspond to any valid open file
            fs->fd[fd] == NULL)   // there was a file, but now its closed.
  {
    ret = 0;
  }

  lock_release(&fs->file_lock);

  return ret;
}
#endif

static inline int get_syscall_arg(void *arg, void *esp, int len)
{
//  int arg = -1;
  if (!copy_from_user(arg, esp, sizeof(int))) {
    sys_exit(-1);
  }

  return arg;
}

void sys_halt(void)
{
  shutdown_power_off();
}

void sys_exit(int __exit_code)
{
  thread_current()->exit_code = __exit_code;
  thread_exit();
}
	
static void SYSCALL_exit(uint32_t *esp)
{
  int __exit_code;
  if (!get_syscall_arg(&__exit_code, ++esp, sizeof(int)))
    sys_exit(-1);

//  printf("dude %s, exit code %d\n\n", __FUNCTION__, __exit_code);
  sys_exit(__exit_code);
}
 
int sys_write(int fd, const void *buf, unsigned len)
{
  struct files_struct *fs = thread_current()->files;
  int ret = -1;

  if (fd == 0) ret = -1;

//  printf("%s %p %d\n", __FUNCTION__, buf, len);

  if (!is_user_access_ok(buf, len)) { //printf("not good not good\n");
      sys_exit(-1);
  }

  if (fd == 1) {
    if (((char *)buf) == NULL) {
      ret = -1;
    }
//    printf("buf is %p\n", buf);
    putbuf(buf, len);
    ret = len;
  }
  else {
    lock_acquire(&fs->file_lock);
    if (fd < 0 || fd >= fs->next_fd) ret = -1;
    else if (fs->fd[fd] == NULL) ret = -1;
    else {
      lock_acquire(&global_fs_lock);
      ret = (int)file_write(fs->fd[fd], buf, len);
      lock_release(&global_fs_lock);
    }
    lock_release(&fs->file_lock);
  }
  return ret;
}

static int SYSCALL_write(uint32_t *esp)
{
  int fd = -1;
  int size = 0;
  char *__user_buf = 0;

  //esp += 4;
//  printf("handler %p {%x, %x, %x, %x} %u\n", esp,*(uint32_t *)(esp-1), *(uint32_t *)(esp), *(uint32_t *)(esp+1),*(uint32_t *)(esp+2), __LINE__);
  get_syscall_arg(&fd, ++esp, sizeof(int));
  get_syscall_arg(&__user_buf, ++esp, sizeof(void *));
  get_syscall_arg(&size, ++esp, sizeof(int));

//  printf("%s %d, %p, %d\n", __FUNCTION__, fd, __user_buf, size);
  return sys_write(fd, __user_buf, size);
} 

int sys_read(int fd, void *buf, unsigned len)
{
  struct files_struct *fs = thread_current()->files;
  int ret = -1;
  unsigned i = 0;

  if (fd == 1) ret = -1;

  if (!is_user_access_ok(buf, len)) {
    sys_exit(-1);
  }

  if (fd == 0) {  // read from STDIN
    char *__buf = (char *)buf;

    for (i = 1; i <= len; ++i) {
      *__buf++ = input_getc();

      if (*(__buf-1) == '\n') break;
    }

    ret = (int)i;  // i bytes read from STDIN
  }
  else {
    lock_acquire(&fs->file_lock);

    if (fd < 0 || fd >= fs->next_fd) ret = -1;
    else if (fs->fd[fd] == NULL) ret = -1;
    else {
      lock_acquire(&global_fs_lock);
      ret = (int)file_read(fs->fd[fd], buf, len);
      lock_release(&global_fs_lock);
    }

    lock_release(&fs->file_lock);
  }

  return ret;
}
    
static int SYSCALL_read(uint32_t *esp)
{
  int fd;
  void *buf;
  unsigned size;

//  hex_dump(0,esp, 128, 1); 
  //esp += 4;
  get_syscall_arg(&fd, ++esp, sizeof(int));
  get_syscall_arg(&buf, ++esp, sizeof(void *));
  get_syscall_arg(&size, ++esp, sizeof(unsigned));
//  printf("%s %d %p %u\n", __FUNCTION__, fd, buf, size);
  return (int)sys_read(fd, buf, size);
}

static int SYSCALL_wait(uint32_t *esp)
{
  tid_t tid;

  get_syscall_arg(&tid, ++esp, sizeof(tid_t));
//  printf("%s,%u] tid = %d\n", __FUNCTION__, __LINE__, tid);  
  return process_wait(tid);
}

static tid_t
SYSCALL_exec(uint32_t *esp)
{
  char *cmd = 0;
  get_syscall_arg(&cmd, ++esp, sizeof(char *));
  if (!sane_file_name(cmd)) return TID_ERROR;
//  printf("exec on %s\n", cmd);
  return process_execute(cmd);
}

int sys_create(const char *file_name, int initial_size)
{
  int ret = 0;

  if (!sane_file_name(file_name))
     sys_exit(-1);

  if (initial_size >= 0 )
  {
    lock_acquire(&global_fs_lock);
    ret = filesys_create(file_name, initial_size);  
    lock_release(&global_fs_lock);
  }
  return ret;
}

static bool
SYSCALL_create(uint32_t *esp)
{
  char *file_name = 0;
  int initial_size = 0;

  //esp += 3;
// hex_dump(esp, esp, 160, 1);
  get_syscall_arg(&file_name, ++esp, sizeof(char *));
  get_syscall_arg(&initial_size, ++esp, sizeof(int));

//  printf("%s %p, %s init_size %d %u\n", __FUNCTION__, file_name, (char *)file_name, initial_size, __LINE__);

  return sys_create(file_name, initial_size);
}

int sys_open(const char *file_name)
{
 if (!sane_file_name(file_name)) sys_exit(-1);

  lock_acquire(&global_fs_lock);
  struct file *one_file = filesys_open(file_name);
  lock_release(&global_fs_lock);

  if (one_file == NULL) return -1;

  // insert into process file struct
  return insert_filp(thread_current()->files, one_file);
}

static int
SYSCALL_open(uint32_t *esp)
{
  char *file_name = 0;
// hex_dump(0, esp, 128, 1);
  get_syscall_arg(&file_name, ++esp, sizeof(char *));
//  printf("%s %p = %s %u\n", __FUNCTION__, file_name, file_name, __LINE__);
  return sys_open(file_name);  
}

int sys_filesize(int fd)
{
  int ret = -1;
  struct files_struct *fs = thread_current()->files;

  if (!fs && fd < 2) sys_exit(-1);

  lock_acquire(&fs->file_lock);
  if (fd < fs->next_fd && fs->fd[fd] != NULL) {
    lock_acquire(&global_fs_lock);
      ret = (int)file_length(fs->fd[fd]);
    lock_release(&global_fs_lock);
  }
  lock_release(&fs->file_lock);

  return ret;
}

static int
SYSCALL_filesize(uint32_t *esp)
{
  int fd = -1;

  get_syscall_arg(&fd, ++esp, sizeof(int));
//  printf("%s fd %d\n", __FUNCTION__, fd);
  return sys_filesize(fd);
}


void sys_seek(int fd, int pos)
{
  struct files_struct *fs = thread_current()->files;

  if (fs == NULL) return;
  if (fd < 2 ) sys_exit(-1);

  lock_acquire(&fs->file_lock);

  if (fd < fs->next_fd && fs->fd[fd] != NULL) {
    lock_acquire(&global_fs_lock);
    file_seek(fs->fd[fd], pos);
    lock_release(&global_fs_lock);
  }

  lock_release(&fs->file_lock);
}

static void
SYSCALL_seek(uint32_t *esp)
{
  int fd = -1;
  int pos = 0;
//  hex_dump(0, esp, 160, 1);
  //esp += 3;
  get_syscall_arg(&fd, ++esp, sizeof(int)); 
  get_syscall_arg(&pos, ++esp, sizeof(int)); 

//  printf("%s fd %x seek request to %u\n", __FUNCTION__, fd, pos);
  sys_seek(fd, pos);
}

int sys_tell(int fd)
{
  struct files_struct *fs = thread_current()->files;
  int ret = -1;

  if (fd < 2 || fs == NULL) sys_exit(-1);

  lock_acquire(&fs->file_lock);

  if (fd < fs->next_fd && fs->fd[fd] != NULL) {
    ret = fs->fd[fd]->pos;
  } 
  else {
    ret = -1;
  }

  lock_release(&fs->file_lock);
//  printf("%s %d\n", __FUNCTION__, ret);
  return ret;
}

static int
SYSCALL_tell(uint32_t *esp)
{
  int fd;
  
  get_syscall_arg(&fd, ++esp, sizeof(int)); 
//  printf("%s tell %d\n", __FUNCTION__, fd);

  return sys_tell(fd);  
}

static void
SYSCALL_close(uint32_t *esp)
{
  int fd;

  get_syscall_arg(&fd, ++esp, sizeof(int));

	lock_acquire(&global_fs_lock);
  sys_close(thread_current(), fd);
	lock_release(&global_fs_lock);
}

int sys_remove(const char *file_name)
{
  int ret = 0;

  if (!sane_file_name(file_name)) return 0;

  lock_acquire(&global_fs_lock);
  ret = filesys_remove(file_name);
  lock_release(&global_fs_lock);

  return ret;
}

static bool
SYSCALL_remove(uint32_t *esp)
{
  char *file_name = 0;
  get_syscall_arg(&file_name, ++esp, sizeof(char *));
//  printf("%s name %s\n", __FUNCTION__, file_name);

  return sys_remove(file_name);
}


static void
syscall_handler (struct intr_frame *f) 
{
  int syscall_no = -1;
  uint32_t *esp = (uint32_t *)(f->esp);
  uint32_t ret = 0;

//  printf ("system call! by %s\n", thread_current()->name);

  ASSERT(f != NULL);
  get_syscall_arg(&syscall_no, esp, sizeof(int));

  switch (syscall_no) {
    case SYS_HALT:
    sys_halt();
    break;

    case SYS_EXIT:
    SYSCALL_exit(esp);
    break;

    case  SYS_EXEC:
    ret = SYSCALL_exec(esp);
    break;

    case SYS_WAIT:
    ret = SYSCALL_wait(esp);
    break;

    case SYS_CREATE:
    ret = SYSCALL_create(esp);
    break;

    case SYS_READ:
    ret = SYSCALL_read(esp);
    break;

    case SYS_WRITE:
    ret = SYSCALL_write(esp);
    break;

    case SYS_OPEN:
    ret = SYSCALL_open(esp);
    break;

    case SYS_REMOVE:
    ret = SYSCALL_remove(esp);
    break;

    case SYS_FILESIZE:
    ret = SYSCALL_filesize(esp);
    break;

    case SYS_SEEK:
    SYSCALL_seek(esp);
    break;

    case SYS_TELL:
    ret = SYSCALL_tell(esp);
    break;

    case SYS_CLOSE:
    SYSCALL_close(esp);
    break;

    default:
   //   printf("Invalid system call nr %u %d\n", f->vec_no, syscall_no);
      ret = -1;
    break;
  }
  f->eax = ret;
}
